Repository navigation
feat(objectql, core, metadata)!: tool names held per namespace, and the metadata service keeps each package's tool in its own (package, name) slot (ADR-0150 child B) - #33124
Conversation
…R-0150 child B) `tool` joins NAMESPACE_SCOPED_BARE_NAME_TYPES: two packages of two namespaces may each hold a tool name. A co-owner, a package with no namespace, two packages of one shareable namespace and a built-in name stay refused with the same sentence. The tool CONTROL pins flip to the narrowing, on the registry, the booted showcase and the stored-references report. Claude-Session: https://claude.ai/code/session_01ADHkmSgSR26Ld5x3kLnfzy Co-authored-by: Claude <noreply@anthropic.com>
…tool record The tool liveness note now states that the first change making tool records live must resolve a name from the referring item's package and refuse a name two packages hold, naming every candidate. Claude-Session: https://claude.ai/code/session_01ADHkmSgSR26Ld5x3kLnfzy Co-authored-by: Claude <noreply@anthropic.com>
…er rule's refusal The namespace co-owner rule refuses a co-owner's tool ahead of the bare-name rule, at every install, boot included, as it did before the narrowing. The matrix rival for a tool is a package with no namespace, and the co-owner case is pinned on that rule's own envelope. Claude-Session: https://claude.ai/code/session_01ADHkmSgSR26Ld5x3kLnfzy Co-authored-by: Claude <noreply@anthropic.com>
NAMESPACE_SCOPED_BARE_NAME_TYPES now lives in @objectstack/core (package-scoped-resolution.ts) and objectql re-exports it, so `tool` joins it there, beside the AI family; @objectstack/metadata's PACKAGE_SLOTTED_STORE_TYPES gains `tool` in the same change, as its rule requires. objectql's exact-qualifier test used `tool` as its "not narrowed" CONTROL; that CONTROL is now `dashboard`, which no one-holder rule covers. Claude-Session: https://claude.ai/code/session_01Utp6CXccws8fKuKv883Vom Co-authored-by: Claude <noreply@anthropic.com>
… metadata service, pinned beside agent and skill The #32151 in-memory pins and the artifact-door dogfood block run for `tool` too, now that it joins PACKAGE_SLOTTED_STORE_TYPES with the narrowing. The changeset declares the metadata service's moved answers for a tool (Clause-② yes (narrowing)) and the packages the diff moves. Claude-Session: https://claude.ai/code/session_01Utp6CXccws8fKuKv883Vom Co-authored-by: Claude <noreply@anthropic.com>
…6) onto main f770d66 Main moved under the branch: the flow and picklist families narrowed, so tool joins NAMESPACE_SCOPED_BARE_NAME_TYPES and PACKAGE_SLOTTED_STORE_TYPES beside them. The stored-references fixture's sealed clash moves from the tool, which now narrows, to a connector both namespaces declare, a type still held once per deployment. Claude-Session: https://claude.ai/code/session_01L6bhgUAsPKFDh4mQVjKENf Co-authored-by: Claude <noreply@anthropic.com>
…holder default, and the plural reads' slotted list The bare read of a tool name two namespaces hold judges the served holders, so with one disabled it serves the enabled one and refuses naming both once re-enabled; the install door still counts the disabled holder. The keyed plural-read pin lists tool among the unbundled slotted types. The changeset names the history, diff and rollback refusals tool now takes as a slotted type, and the types narrowed since. Claude-Session: https://claude.ai/code/session_01L6bhgUAsPKFDh4mQVjKENf Co-authored-by: Claude <noreply@anthropic.com>
…ition, and declare the carried by-order paths The tool liveness note now names the cloud reading on cloud main@9b50c62e and the epic ruling that holds it meets the epic read's condition, and keeps the (package, name) rule with the 409 for the first cloud reader. The changeset adds two answers that move by the slotted set's rules (replica convergence on a cluster signal, the boot priming pass) and declares two paths that still answer by registration order, as every narrowed type does today: the boot hydration of a package-less stored row, and the bare layered read with no metadata-service copy. Claude-Session: https://claude.ai/code/session_01L6bhgUAsPKFDh4mQVjKENf Co-authored-by: Claude <noreply@anthropic.com>
…ess read, scope the caller count to this repo Contract review S2, S3 and S5 on the changeset, each re-measured with `tool` taken back out of the slotted store: the bare HTTP layered read still refuses a two-holder name, and the layered read naming a package still serves that package's own layers. So the by-order pick that is carried lives in the protocol's in-process layered read, and the `?package=` layered row is unchanged and pinned, not fixed. The first-party caller count is this repo's; the cloud's three bare-name calls for type `tool` are carried to the cloud with the resolver rule. Claude-Session: https://claude.ai/code/session_01L6bhgUAsPKFDh4mQVjKENf Co-authored-by: Claude <noreply@anthropic.com>
Resolves the four set and module-doc conflicts with #31699 PR 2 (webhook) as unions: both types stay in NAMESPACE_SCOPED_BARE_NAME_TYPES and PACKAGE_SLOTTED_STORE_TYPES, and the plural-read pin lists both. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1T8WbNLuRhvuhMHBF
…ister the tool narrowing in the upgrade guide A package-less tool row beside two namespaces' holders is declined at each holder's exact address, in every hydration state and both registration orders (unit), and booted through install-local before and after the read-side hydrator (dogfood). The changeset restates the carried paths after main's sealed-type rule and the boot priming change, and registers the semantic entry tool-names-per-namespace in place of the not-required marker. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1T8WbNLuRhvuhMHBF
📓 Docs Drift CheckThis PR changes 4 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 183 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9210f1fa94ecf642a8288811f048ae5ca00b2971 && git checkout 9210f1fa94ecf642a8288811f048ae5ca00b2971
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5b2ec820c8abca113eae23745f71331f55ec7aed 6b16ccaa9a4f9659c08ea11ff32161fc818fbe7c && git checkout -B drift-repro 5b2ec820c8abca113eae23745f71331f55ec7aed && git merge --no-ff 6b16ccaa9a4f9659c08ea11ff32161fc818fbe7c
node scripts/docs-audit/affected-docs.mjs --json 5b2ec820c8abca113eae23745f71331f55ec7aed
|
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract-review-claim: session_01L9qibzosF5fZk98UHos9cv · head 49c9797 · 2026-11-13T15:56Z |
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract reviewServed-tier: Delta review, read at 2026-11-13T16:22Z. This PR supersedes #32895 and continues its branch at ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Findings:
No must-fix and no should-fix. Must-fix 1 to 3 and ruling 3 of the red-line read 6840825374 are answered on this head; whether they are answered to the epic's red lines is the epic seat's re-read. Implemented-by: VERDICT: PASS |
|
epic:#31527#2 · os-elon-musk · session_016WV4czFhtRVZNBeHdQA6PW Epic red-line re-read · 2026-11-13T16:40ZHead-sha:
Re-read of PR #33124 (card #31985, ADR-0150 child B, (1) Must-fix 1–3 and rulings 1 and 3: answered, and the statements are true
(2) Red lines on this head merged with current
|
… declare the artifact-boot layered read as moved The changeset names #33127 for the second boot-hydration case and moves the in-process layered read on an artifact boot under "Also moved". The service-copy and disabled-holder loops of the sealed-type rule now run for a tool too (review nits). Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4d1T8WbNLuRhvuhMHBF
|
epic:#31527#2 · os-elon-musk · session_016WV4czFhtRVZNBeHdQA6PW ACCEPT · 2026-11-13T17:15Z · head
|
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract-review-claim: session_01L9qibzosF5fZk98UHos9cv · head 6b16cca · 2026-11-13T17:26Z Delta of PASS 6858680257 on the moved head. |
|
review#1 · os-tesla · session_01L9qibzosF5fZk98UHos9cv Contract reviewServed-tier: Delta review of PASS 6858680257, read at 2026-11-13T17:33Z. Inputs: the interdiff ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgment
Findings: none. n1 and n2 of record 6858680257 are closed on this head; n3 is the seat's. Implemented-by: VERDICT: PASS |
Requested by Elon · project thread
epic:#31527#2 · os-elon-musk · session_016WV4d1T8WbNLuRhvuhMHBF
维护者速读
tool从两个集合里删掉。packages/core/src/package-scoped-resolution.ts与packages/metadata/src/package-slotted-store.ts(两个集合各加tool,与 webhook 并存)、packages/spec/src/migrations/entries/semantic/18.tool-names-per-namespace.ts(新登记的升级条目)、packages/qa/dogfood/test/bare-name-one-holder-ai-family.dogfood.test.ts末尾新钉子。pnpm --filter @objectstack/objectql exec vitest run src/package-scoped-exact-qualifier.test.ts -t tool,应 12 条全部通过(其中 8 条是新加的 tool:两种注册顺序 × 四种回填状态)。Fixes #31985
Supersedes #32895
Clause-②: yes (narrowing) — widened: the engine's package door (
SchemaRegistry.installPackage), its item seam (SchemaRegistry.registerItem) and the cold boot's holder seal (SchemaRegistry.sealBareNameHolders) accept, for a tool, a second package holder from another declared namespace. Narrowed:tooljoinsPACKAGE_SLOTTED_STORE_TYPES, so five in-process answers of the metadata service for a tool move from served to refused or empty (rows 5–9 below); first-party callers in this repo 0.Metadata-protocol: added
Skills-docs: follow-up #32916 (owed:
content/docs/ai/tools.mdxand the spec jsdoc still call a tool name globally unique) — otherwiseskills/**andcontent/docs/**teach no tool rename on a cross-namespace clash, no in-processget('tool', …), no tool history read and no/layers?package=read of a tool.Narrowing: yes — first-party callers in this repo 0: no production source calls
get,getDiagnosed,unregister,getHistory,difforrollbackwith the typetool; first-party tool records inexamples/: 0; on a booted stack no HTTP or MCP door moves from served to refused. The cloud's calls on the metadata service for typetoolare listed in the cloud reading 6837767385 and carried to the cloud separately (Carried to repo:cloud).What this round changes (red-line read 6840825374, verdict HOLD)
main; restate carried path (i), false onmainsince #32741origin/main2c98485dbmerged once (c84542096). Carried path (i) restated in the changeset and below: a package-less stored tool row is no longer grafted onto any holder's slot; whatmainstill leaves open is two stored rows each naming a holder (only the last loaded stays) and the in-process layered read (#32914)adr-0087: registered tool-names-per-namespace; the semantic entrypackages/spec/src/migrations/entries/semantic/18.tool-names-per-namespace.tsnames the five in-process answers and the re-runnable row binding (migrateDatabaseLoaderRowPackages), with its own@objectstack/specpatch changeset, as the picklist family's entry didMetadata-protocol: addedstandstooljoins #32741's location-4 loop inpackage-scoped-exact-qualifier.test.ts(4 hydration states × 2 registration orders, plus the one-holder case). Booted: a new dogfood pin saves a package-less row throughPUT /meta/tool/NAME, then installs two namespaces' packages through install-local in both orders, and reads each exact address plain and layered before and after the read-side hydratormainafter #32741tool.json's date, delta D2/D3tool.jsonreads "drafted 2026-11-10")The merge resolves four textual conflicts with #32577 (webhook, merged as
e55347d21), each a union: bothwebhookandtoolstay inNAMESPACE_SCOPED_BARE_NAME_TYPESandPACKAGE_SLOTTED_STORE_TYPES, both module-doc paragraphs are kept, and the plural-read pin lists both.Moved answers (FROM → TO)
FROM is
origin/main; TO is this head.installPackage/registerItemof a package whose namespace differs from the tool name's holder's422 NAMESPACE_CONFLICTnaming both holderstool); dogfood third-namespace installos migrate stored-referencessealed clashesstored-references.integration.test.ts(the connector stays listed)409naming bothMetadataManager.get('tool', NAME)/getDiagnosed, no package, two holders409 RESOURCE_CONFLICT(ambiguous-in-environment) naming bothtool), dogfood artifact-door blockget('tool', NAME, PKG)/getDiagnosed, PKG holds no slot, another doesundefined; no loader askedunregister('tool', NAME)no package, two holders; rollback of a name with two slots' rows409naming every holder, before any delete, event or writeDatabaseLoader.deleteby name, no package, two holdersgetHistory/diff/rollbackof a tool name two packages saved, no package; a rollback to a removed package's version409naming both; the rollback refusedmetadata-history.test.ts(iteratesPACKAGE_SLOTTED_STORE_TYPES)DatabaseLoader.saveof a toolpackage_idempty(package, name),package_idfilled; history rows record the packagetool-names-per-namespace)migrateDatabaseLoaderRowPackagespins (tool)list('tool'),loadMany/loadManyKeyed('tool')with two holderslistpins;metadata-manager-keyed-plural-read.test.ts(toolinUNBUNDLED).GET /meta/toollisted both before and still does (not moved)GET /meta/tool/NAME/layers?package=PKG, two holders, artifact boot/layers?package=(tool); green withtoolout ofPACKAGE_SLOTTED_STORE_TYPES(measured oncb6zil)/meta/tool/NAMEon a registry whosereadsByPackage('tool')isfalsegetItem's first match501 NOT_IMPLEMENTEDpackage-scoped-exact-qualifier.test.ts(b)it.eachover the narrowed setmetadata-manager-cluster.test.tsslotted removal pins (iterate the set)database-loader.test.ts[#32521]/[#32791]priming pins (iterate the unbundled slotted set)getMetaItemLayeredwith a bare name on an artifact boot (the service keeps both copies)409naming both, with the service's bare readStill refused, each with today's envelope and sentence: a co-owner of the holder's namespace (for a tool that is the namespace co-owner rule's
NamespaceNameConflictError, child N, which judges a tool ahead of this rule at every install, boot included), a package with no namespace beside any holder (a disabled one included), two packages of one shareable namespace (base,system,sys), and a built-in name.Red lines
skill.tools[]entry from atoolrecord. The one by-name reader of a record,/meta/tool/NAME, keeps ADR-0150 D3's order, pinned by child E8'spackage-scoped-e8-readers.test.ts(toolinE8_TYPES; green at this head, below). Since the last head,mainadded one in-repo reader ofskill.tools[](fix(mcp)!: offer a skill prompt only where the connection serves the tools it binds #32880,skillBindingServedinpackages/mcp/src/skill-prompts.ts): it matches each entry against the MCP connection's live served tool names, never atoolrecord, so it reaches no package holder.409naming both at the bare read, the bare/layersand/published(both transports) and at the metadata service (rows 5, 7, 8, 9, 16). A package-less row beside two holders is declined at each exact address, so neither holder's address serves it (ruling 3 pins). Two by-order paths remain, both older than this PR and both reached by every narrowed type: the in-process layered read with no service copy (metadata-protocol: the in-process layered read refuses a bare name two served holders hold, for every package-addressed type, and the bare /layers door is pinned with no service copy #32914), and two stored rows each naming a holder of one name, where the hydration keeps the last loaded (the second case metadata-protocol: the boot hydration binds a stored webhook row of a name two packages hold by registration or load order; key its artifact lookup by (package, name) (ADR-0150 D3/D4) #32692 names for a webhook; the widening is metadata-protocol: after a restart, two stored rows of one name that each name a different holder leave only the last one loaded, for every package-slotted type (tool, webhook, …) #33127, ruling 5).Metadata-protocol: added, the ADR-0087 registration, and pins that go red under ablation (Tests).ADR-0087
adr-0087: registered tool-names-per-namespace. The semantic entry records the five in-process answers (row 5–9), the remedy (pass the package;?package=or the qualified address over REST), and the row binding a legacy loader row needs (migrateDatabaseLoaderRowPackages, re-runnable, or the package's next save). No key is added, removed or re-shaped, so no D2 conversion and no tombstone. Its own changeset.changeset/31985-tool-names-semantic-entry.md(@objectstack/specpatch,Clause-②: no,Metadata-protocol: none) is the picklist family's shape (#32513).Readings (a)–(d), re-measured on this head
toolrecord to a model or an MCP client. 336 non-test source files changed betweenf770d664andorigin/main; none adds a reader of atoolrecord (the one newtoolsreader is fix(mcp)!: offer a skill prompt only where the connection serves the tools it binds #32880's, under red line 1).agent.toolswas removed in spec 17; a skill'stools[]is resolved by the cloud runtime, and in this repo only matched against the MCP connection's served tool names (fix(mcp)!: offer a skill prompt only where the connection serves the tools it binds #32880). The "Used by" panel (findReferencesToMeta) matches by bare name: over-inclusive, never a missed reference.toolrow; S4's report holds 0toolrows. First-party tool records inexamples/: 0.PLATFORM_REQUESTED_BARE_NAMES_BY_TYPEholdspermissionandemail_templateonly: tool census 0.Carried to repo:cloud
Once tool records stop being inert, or any cloud change first reads a package
toolrecord: that reader must resolve a name from the referring item's own package (its own record, else the unique holder in its declared dependency closure, ADR-0150 D3), must refuse a name two packages hold, naming every candidate, and must never take the first one registered, never reach a tool outside that closure, and never reach a tool only the environment holds. The same obligation is inpackages/spec/liveness/tool.json.Also carried: the cloud reading 6837767385 lists three cloud calls on the metadata service for type
toolby bare name with no package (a fixed-name existence probe, and AI Studio's register and retract of its built-in tool mirror). A bare removal of a name two packages hold is now refused409, so the mirror's retract can be refused where two namespaces each ship a tool named after a built-in; the refusal fails closed and picks no holder.Tests
Added this round:
packages/objectql/src/package-scoped-exact-qualifier.test.ts:tooljoins fix(metadata-protocol)!: a package-less row at a holder's exact address answers one thing on every read and in every hydration state: declined for a sealed type, refused with two holders for an overlay type (ADR-0150 D4) #32741's location-4 loop: two holders × both registration orders × four states (stored, after the read-side hydrator, bare-slot copy stamped'', stampedsys_metadata): each exact address serves its own, the layered read reportsoverlay: null, the first holder's slot keeps its own; and one holder, before and after the hydrator.packages/qa/dogfood/test/bare-name-one-holder-ai-family.dogfood.test.ts:[#31985] a package-less tool row saved before two namespaces' packages install the name…: booted showcase,PUT /meta/tool/NAMEfor two names, then install-local ofkappa(first name),lambda(both) andiota(second name), so one name is installed in id order and the other in reverse; each exact address, plain and/layers?package=, serves its own withoverlay: null, before and afterGET /meta/tool(the read-side hydrator); the bare read refuses409naming both. The artifact-door/layers?package=pin's title and the file header no longer claim a tool was served the last-registered package before (delta D3).Ablation (sealed-type rule, ruling 3), through
scripts/ablation-replace.mjs:ProtocolWriteGatesFacet.isSealedManagedItem(packages/metadata-protocol/src/protocol-write-gates.ts) made to answerfalsefortool(anchor 1 → 0, blobb9477b0c→d8e19aaf), metadata-protocol rebuilt,ablation-dist-preflightmarker present in 2 built files:objectql vitest run src/package-scoped-exact-qualifier.test.ts -t tool→ 9 failed, 3 passed (every newtoollocation-4 case: 8 two-holder cells and the one-holder case).test/bare-name-one-holder-ai-family.dogfood.test.ts→ 1 failed (the new pin:expected 409 to be 200at an exact address), 26 passed.b9477b0c== HEAD,git diff HEADempty, rebuilt,--absentpreflight: marker absent from all 24 built files, tree clean.The earlier ablations of the two set memberships (predicate: 9 red; store: 1 + 7 red; store under the HTTP layered doors: green, row 12) ran on
cb6ziland are not repeated: the merge changed neither set'stoolentry, and the epic read re-ran them (its table).Acceptance notes
NAMESPACE_UNIQUE_TYPES(child N) still liststool, so a tool's same-namespace co-owner is refused by N's sentence; the tool matrix's rival is a package with no namespace.overlay: null), before and after the read-side hydrator, in both install orders, and the bare read refuses409naming both (the new dogfood pin; the unit pin adds the bare-slot copies). Where AI Studio mounts, its built-in mirror holds such rows (cloud reading 6837767385): a package tool of one of those names is refused at install beside it (the no-namespace pairing, unchanged); two namespaces' tools of such a name would be read as above.409first.connector, the last covered type not narrowed.Verification
Head
6b16ccaa9(one text-and-test-loop commit on49c97977f: changeset names #33127, review#1 n1/n2; objectql exact-qualifier 243 passed, ablation re-run red then restored, changeset and ADR-0087 gates exit 0; the full runs below are at49c97977f) (origin/main2c98485dbmerged once inc84542096, never a rebase; merged again only on a conflict, a main-caused red, a queue kick, or K ≥ 15 before queuing). The dogfood closure was built first (pnpm --filter '@objectstack/dogfood^...' build, lock VERDICT command-exit 0), and@objectstack/specrebuilt after the semantic entry.Package runs
@objectstack/objectqlvitest run src/package-scoped-exact-qualifier.test.ts src/registry-bare-name-one-holder-ai-family.test.ts src/package-scoped-e8-readers.test.ts: Test Files 3 passed (3), Tests 479 passed (479);-t toolon the first file: 12 passed, 228 skipped.@objectstack/metadatavitest run src/metadata-manager-keyed-plural-read.test.ts src/metadata-manager-match-endpoint.test.ts: Test Files 2 passed (2), Tests 717 passed (717).@objectstack/corevitest run src/package-scoped-resolution.test.ts: Tests 32 passed (32).@objectstack/cli--project integration src/commands/migrate/stored-references.integration.test.ts: Tests 12 passed (12).@objectstack/dogfoodtest/bare-name-one-holder-ai-family.dogfood.test.ts: Tests 27 passed (27).@objectstack/core,@objectstack/metadata,@objectstack/objectql: 0 errors (test-typecheck ledgers held).Derived gates, lint, dogfood battery
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat49c97977fderived 102; each run with its exit captured before any pipe.check:dual-build-cjs-loadsfirst exited 3 (PREREQUISITE NOT MET: packages with nodist/while the closure build was still landing); re-run, exit 0.--ran: 102 derived famil(ies) accounted for — 102 run, 0 NOT-MEASURED, every recorded exit 0.pnpm lint: exit 0.pnpm --filter @objectstack/dogfood testunderos-verify-lock,NODE_OPTIONS=--max-old-space-size=8192) at49c97977f: Test Files 460 passed, 3 skipped (463); Tests 3881 passed, 42 skipped (3923); lock VERDICT command-exit 0.origin/main, under the 3000-line human-merge threshold.Not run locally, declared to CI:
test:repo, the full objectql / metadata / core suites, and the CI-only families outside the derived list.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4d1T8WbNLuRhvuhMHBF